home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Development Tools & Languages / Dylan Related / Mindy / Mindy 1.2 - portable sources / libraries / string-ext / parse.dylan < prev    next >
Encoding:
Text File  |  1995-03-15  |  11.2 KB  |  338 lines  |  [TEXT/ttxt]

  1. module: regular-expressions
  2. author: Nick Kramer (nkramer@cs.cmu.edu)
  3. copyright:  Copyright (C) 1994, Carnegie Mellon University.
  4.             All rights reserved.
  5. rcs-header: $Header: parse.dylan,v 1.1 94/11/08 22:57:41 nkramer Exp $
  6.  
  7. //======================================================================
  8. //
  9. // Copyright (c) 1994  Carnegie Mellon University
  10. // All rights reserved.
  11. // 
  12. // Use and copying of this software and preparation of derivative
  13. // works based on this software are permitted, including commercial
  14. // use, provided that the following conditions are observed:
  15. // 
  16. // 1. This copyright notice must be retained in full on any copies
  17. //    and on appropriate parts of any derivative works.
  18. // 2. Documentation (paper or online) accompanying any system that
  19. //    incorporates this software, or any part of it, must acknowledge
  20. //    the contribution of the Gwydion Project at Carnegie Mellon
  21. //    University.
  22. // 
  23. // This software is made available "as is".  Neither the authors nor
  24. // Carnegie Mellon University make any warranty about the software,
  25. // its performance, or its conformity to any specification.
  26. // 
  27. // Bug reports, questions, comments, and suggestions should be sent by
  28. // E-mail to the Internet address "gwydion-bugs@cs.cmu.edu".
  29. //
  30. //======================================================================
  31.  
  32. // This is a program to parse regular expressions. The grammar I'm using is:
  33. //
  34. //      <regexp> ::= <alternative> | <alternative>|<regexp>
  35. //
  36. //      <alternative> ::= <quantified-atom> | <quantified-atom><alternative>
  37. //
  38. //      <quantified-atom> ::= <atom> | <atom><quantifier>
  39. //
  40. //      <quantifier> ::= * | + | ? | {n} | {n,} | {n, m}
  41. //            (where n and m are decimal integers)
  42. //
  43. //      <atom> ::= (<regexp>) | <extended-character>
  44. //
  45. // See "Programming perl", p. 103-104 for more details.
  46. //
  47. // Because an assertion is a type of <extended-character>, this will
  48. // parse a "quantified assertion", which really isn't a legal regular
  49. // expression component.  Match.dylan could go into an infinite loop
  50. // if given this.
  51.  
  52. define constant <integer?> = union(<integer>, singleton(#f));
  53.  
  54. define abstract class <parsed-regexp> (<object>)
  55. end class <parsed-regexp>;
  56.  
  57. define class <mark> (<parsed-regexp>)
  58.   slot child :: <parsed-regexp>,  required-init-keyword: child:    ;
  59.   slot group-number :: <integer>, required-init-keyword: group:    ;
  60. end class <mark>;
  61.  
  62. define class <union> (<parsed-regexp>)          //    |
  63.   slot left  :: <parsed-regexp>, required-init-keyword: left:      ;
  64.   slot right :: <parsed-regexp>, required-init-keyword: right:     ;
  65. end class <union>;
  66.  
  67. define class <alternative> (<parsed-regexp>)    // concatenation
  68.   slot left :: <parsed-regexp>,  required-init-keyword: left:      ;
  69.   slot right :: <parsed-regexp>, required-init-keyword: right:     ;
  70. end class <alternative>;
  71.  
  72. define class <parsed-assertion> (<parsed-regexp>)
  73.   slot asserts :: <symbol>, required-init-keyword: assertion: ;
  74. end class <parsed-assertion>;
  75.  
  76. define class <quantified-atom> (<parsed-regexp>)
  77.   slot atom :: <parsed-regexp>, required-init-keyword: atom: ;
  78.   slot min-matches :: <integer>,  init-value: 0,  init-keyword: min: ;
  79.   slot max-matches :: <integer?>, init-value: #f, init-keyword: max: ;
  80. end class <quantified-atom>;
  81.  
  82. define abstract class <parsed-atom> (<parsed-regexp>)
  83. end class <parsed-atom>;
  84.  
  85. define class <parsed-character> (<parsed-atom>)
  86.   slot character :: <character>, required-init-keyword: character: ;
  87. end class <parsed-character>;
  88.  
  89. define class <parsed-set> (<parsed-atom>)
  90.   slot char-set :: <character-set>, required-init-keyword: set: ;
  91. end class <parsed-set>;
  92.  
  93. define class <parsed-backreference> (<parsed-atom>)
  94.   slot group-number :: <integer>, required-init-keyword: group: ; 
  95. end class <parsed-backreference>;
  96.  
  97.  
  98. // <parse-info> contains some information about the current regexp
  99. // being parsed.  Using a structure is slightly nicer than having
  100. // global variables..
  101. //
  102. define class <parse-info> (<object>)
  103.   slot backreference-used, init-value: #f;
  104.      // Whether or not the function includes \1, \2, etc in the regexp.
  105.      // This is different from return-marks, which determines whether the
  106.      // user wants to know about the marks.
  107.   slot has-alternatives, init-value: #f;
  108.   slot has-quantifiers, init-value: #f;
  109.   slot current-group-number, init-value: 0;
  110.   slot set-type :: <class>, init-keyword: set-type: ;
  111. end class <parse-info>;
  112.  
  113.  
  114. define method parse (s :: <string>, character-set-type :: <class>);
  115.   let parse-info = make(<parse-info>, set-type: character-set-type);
  116.   let parse-string = make(<parse-string>, string: s);
  117.   let parse-tree = make(<mark>, group: 0, 
  118.             child: parse-regexp(parse-string, parse-info));
  119.   values(parse-tree,
  120.      parse-info.current-group-number,
  121.      parse-info.backreference-used,
  122.      parse-info.has-alternatives,
  123.      parse-info.has-quantifiers);
  124. end method parse;
  125.  
  126.  
  127. define method parse-regexp(s :: <parse-string>, info :: <parse-info>)
  128.     => parsed-regexp :: <parsed-regexp>;
  129.   let alternative = parse-alternative(s, info);
  130.   if (lookahead(s) = '|')
  131.     info.has-alternatives := #t;
  132.     make(<union>, left: alternative, right: parse-regexp(consume(s), info));
  133.   else
  134.     alternative;
  135.   end if;
  136. end method parse-regexp;
  137.  
  138.  
  139. define method parse-alternative(s :: <parse-string>, info :: <parse-info>)
  140.     => parsed-regexp :: <parsed-regexp>;
  141.   let term = parse-quantified-atom(s, info);
  142.   if (member?(lookahead(s), #(#f, '|', ')')))
  143.     term;
  144.   else
  145.     make(<alternative>, left: term, right: parse-alternative(s, info));
  146.   end if;
  147. end method parse-alternative;
  148.  
  149.  
  150. define method parse-quantified-atom(s :: <parse-string>, info :: <parse-info>)
  151.     => parsed-regexp :: <parsed-regexp>;
  152.   let atom = parse-atom(s, info);
  153.   let char = lookahead(s);
  154.   select (char by \=)
  155.     '*' =>
  156.       info.has-quantifiers := #t;
  157.       consume(s);
  158.       make(<quantified-atom>, min: 0, atom: atom);
  159.  
  160.     '+' =>
  161.       info.has-quantifiers := #t;
  162.       consume(s);
  163.       make(<quantified-atom>, min: 1, atom: atom);
  164.  
  165.     '?' =>
  166.       info.has-quantifiers := #t;
  167.       consume(s);
  168.       make(<quantified-atom>, min: 0, max: 1, atom: atom);
  169.  
  170.     '{' =>
  171.       info.has-quantifiers := #t;
  172.       consume(s);
  173.       let first-string = make(<deque>);
  174.       let second-string = make(<deque>);
  175.       let has-comma = #f;
  176.       for (c = lookahead(s) then lookahead(s), until c = '}')
  177.     consume(s);
  178.     if (c = ',')  
  179.       has-comma := #t;
  180.     elseif (has-comma)  
  181.       push-last(second-string, c);
  182.     else 
  183.       push-last(first-string, c);
  184.     end if;
  185.       end for;
  186.       consume(s);         // Eat closing brace
  187.       make(<quantified-atom>, atom: atom, 
  188.        min: string-to-integer(first-string),
  189.        max:  if (~has-comma)    
  190.            string-to-integer(first-string)
  191.          elseif (empty?(second-string))   
  192.            #f
  193.          else
  194.            string-to-integer(second-string) 
  195.          end if);
  196.  
  197.     otherwise =>
  198.       atom;
  199.   end select;
  200. end method parse-quantified-atom;
  201.  
  202.  
  203. define method parse-atom (s :: <parse-string>, info :: <parse-info>)
  204.     => parsed-regexp :: <parsed-regexp>;
  205.   let char = lookahead(s);
  206.   select (char)
  207.     '(' =>
  208.       consume(s);   // Consume beginning paren
  209.       info.current-group-number := info.current-group-number + 1;
  210.       let this-group = info.current-group-number;
  211.       let regexp = parse-regexp(s, info);
  212.       if (lookahead(s) ~= ')')
  213.     error("Unbalanced parens in regexp");
  214.       end if;
  215.       consume(s);   // Consume end paren
  216.       make(<mark>, child: regexp, group: this-group);
  217.  
  218.     ')' =>
  219.       #f;             // Need something to terminate upon seeing a close paren
  220.  
  221.     #f  =>
  222.       #f;   // Signal error?  (end of stream)
  223.  
  224.     '*', '|', '+' =>
  225.       #f;
  226.  
  227.     '\\' =>
  228.       consume(s);        // Consume the backslash
  229.       parse-escaped-character(s, info);
  230.  
  231.     '[' =>
  232.       consume(s);        // Eat the opening brace
  233.       let set-string = make(<deque>);      // Need something that'll 
  234.                                            // preserve the right ordering
  235.       for (char = lookahead(s) then lookahead(s), until char = ']')
  236.     consume(s);                    // eat char
  237.     push-last(set-string, char);
  238.     if (char = '\\')
  239.       push-last(set-string, lookahead(s));
  240.       consume(s);     // eat thing after backslash
  241.     end if;
  242.       end for;
  243.       consume(s);     // Eat ending brace
  244.       make(<parsed-set>, set: make(info.set-type, description: set-string));
  245.  
  246.     '.' =>
  247.       consume(s);
  248.       dot;
  249.  
  250.     '^' =>
  251.       consume(s);
  252.       make(<parsed-assertion>, assertion: #"beginning-of-string");
  253.  
  254.     '$' =>
  255.       consume(s);
  256.       make(<parsed-assertion>, assertion: #"end-of-string");
  257.   
  258.       // Insert more special characters here
  259.  
  260.     otherwise =>
  261.       let char = lookahead(s);
  262.       consume(s);
  263.       make(<parsed-character>, character: char);
  264.   end select;
  265. end method parse-atom;
  266.  
  267.  
  268. define constant any-char 
  269.   = make(<case-sensitive-character-set>, description: "^\n");
  270.  
  271. // The useful definitions of all these is in as(<character-set>)
  272. //
  273. define constant digit-chars
  274.   = make(<case-sensitive-character-set>, description: "\\d");
  275. define constant not-digit-chars
  276.   = make(<case-sensitive-character-set>, description: "^\\d");
  277. define constant word-chars
  278.   = make(<case-sensitive-character-set>, description: "\\w");
  279. define constant not-word-chars
  280.   = make(<case-sensitive-character-set>, description: "^\\w");
  281. define constant whitespace-chars
  282.   = make(<case-sensitive-character-set>, description: "\\s");
  283. define constant not-whitespace-chars
  284.   = make(<case-sensitive-character-set>, description: "^\\s");
  285.  
  286.  
  287. define constant dot = make(<parsed-set>, set: any-char);
  288.  
  289. define constant dot-star = make(<quantified-atom>, min: 0, max: #f,
  290.                 atom: dot);
  291.  
  292.  
  293. // This only handles escaped characters *outside* of a character
  294. // set. Inside of a character set is a whole different story.
  295. //
  296. define method parse-escaped-character (s :: <parse-string>, 
  297.                        info :: <parse-info>)
  298.     => parsed-regexp :: <parsed-regexp>;
  299.   let next-char = lookahead(s);
  300.   consume(s);
  301.   select (next-char)
  302.     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' =>
  303.       info.backreference-used := #t;
  304.       make(<parsed-backreference>, group: digit-to-integer(next-char));
  305.  
  306.     'n' =>   make(<parsed-character>, character: '\n');   // Newline
  307.     't' =>   make(<parsed-character>, character: '\t');   // Tab
  308.     'f' =>   make(<parsed-character>, character: '\f');   // Formfeed
  309.     'r' =>   make(<parsed-character>, character: '\r');   // Carriage return
  310.  
  311.     'b' =>   make(<parsed-assertion>, assertion: #"word-boundary");
  312.     'B' =>   make(<parsed-assertion>, assertion: #"not-word-boundary");
  313.        // Beginning and end of string are not escaped
  314.  
  315.     'd' =>   make(<parsed-set>, set: digit-chars);
  316.     'D' =>   make(<parsed-set>, set: not-digit-chars);
  317.     'w' =>   make(<parsed-set>, set: word-chars);
  318.     'W' =>   make(<parsed-set>, set: not-word-chars);
  319.     's' =>   make(<parsed-set>, set: whitespace-chars);
  320.     'S' =>   make(<parsed-set>, set: not-whitespace-chars);
  321.  
  322.     // Insert more escaped characters here
  323.  
  324.     otherwise =>
  325.       make(<parsed-character>, character: next-char);
  326.   end select;
  327. end method parse-escaped-character;
  328.  
  329. define method is-anchored? (regexp :: <parsed-regexp>)
  330.  => (result :: <boolean>);
  331.   select (regexp by instance?)
  332.     <mark> => is-anchored?(regexp.child);
  333.     <alternative> => is-anchored?(regexp.left);
  334.     <parsed-assertion> => regexp.asserts == #"beginning-of-string";
  335.     otherwise => #f;
  336.   end select;
  337. end method is-anchored?;
  338.